/* -*- mode: c++; coding: utf-8-unix -*-
 */

#include "Image.h"
#include <assert.h>
#include <ImageMagick/Magick++.h>
#include "Log.h"
#include <typeinfo>
#include <cxxabi.h>
#include <math.h>
#include <map>
#include <limits>
#include <string.h>

namespace m = Magick;
using namespace img;
using namespace std;

static const size_t PSIZE = 3;
static const double MAX_PIXEL_VALUE = 1;

static const ch_pixel_type SCALE_FACTOR = MAX_PIXEL_VALUE/MaxRGB;
static const ch_pixel_type INV_SCALE_FACTOR = MaxRGB/MAX_PIXEL_VALUE;
static const ch_pixel_type SHIFT = 0;

class Image::Pimp
{
public:
	bool grayscale;
	size_type w, h;
	const string outfilename;

	Pimp(bool grayscale, size_type width, size_type height, const string &outfname);
	virtual ~Pimp();
	virtual ch_pixel_type pixel(Channel ch, size_type x, size_type y) = 0;
	virtual void setPixel(Channel ch, size_type x, size_type y,
			      ch_pixel_type  value) = 0;
	virtual void save(double) = 0;
};
class MPimp : public Image::Pimp
{
	inline size_t offset(Channel ch, size_type x, size_type y) const
	{
		return x*w+y;
	}
	inline ch_pixel_type norm(ch_pixel_type c) const
	{
		return c * SCALE_FACTOR + SHIFT;
	}
	inline ch_pixel_type denorm(ch_pixel_type c) const
	{
		return c * INV_SCALE_FACTOR - SHIFT;
	}

public:
	m::Image *image;
	const m::PixelPacket* pixels;
	MPimp(bool grayscale, m::Image *img, const string &outfname);
	~MPimp()
	{
		USELOG();

		LOG_DEBUG("pixels: " << pixels);
		if (pixels)
		{
			image->syncPixels();
		}
		LOG_DEBUG("Deleting image: " << image);
		if (image)
			delete image;
	}
	
	ch_pixel_type pixel(Channel ch, size_type x, size_type y);
	void setPixel(Channel ch, size_type x, size_type y, ch_pixel_type value);
	void save(double);
};
class DPimp : public Image::Pimp
{
	size_type len;
	inline size_t offset(Channel ch, size_type x, size_type y) const
	{
		if (grayscale) return (x*w+y)*PSIZE;
		else return (x*w+y)*PSIZE+(int)ch-1;
	}
public:
	double *dimage;
	DPimp(bool grayscale, size_t width, size_t height, const string &outfname);
	~DPimp() { if (dimage) delete[] dimage; }

	ch_pixel_type pixel(Channel ch, size_type x, size_type y);
	void setPixel(Channel ch, size_type x, size_type y, ch_pixel_type value);
	void save(double);
};

Image::Pimp::Pimp(bool graysc, size_type width, size_type height, const string &outfname)
	: grayscale(graysc), w(width), h(height),
	  outfilename(outfname) {}
Image::Pimp::~Pimp() {}
MPimp::MPimp(bool graysc, m::Image *img, const string &outfname)
	: Pimp(graysc, img->size().width(), img->size().height(), outfname),
	  image(img), pixels(img->getConstPixels(0, 0, w, h)) {}
DPimp::DPimp(bool graysc, size_t width, size_t height, const string &outfname)
	: Pimp(graysc, width, height, outfname),
	  len(width * height * PSIZE),
	  dimage(new double[len])
{
	memset(dimage, 0, len);
}

Image::Image(bool grayscale, const string &filename)
	: _pimp(new MPimp(grayscale, new m::Image(filename), ""))
{
}
Image::Image(bool grayscale, size_type width, size_type height,
	     const string &outfname)
	: _pimp(new DPimp(grayscale, width, height, outfname))
{
}
Image::~Image() throw ()
{
	delete _pimp;
}

size_type Image::width() const { return _pimp->w; }
size_type Image::height() const { return _pimp->h; }

ch_pixel_type MPimp::pixel(Channel ch, size_type x, size_type y)
{	
	const m::PixelPacket &p = pixels[offset(ch, x, y)];
	ch_pixel_type retval;
	switch (ch)
	{
	case Gray:
	case Red:
		retval = p.red; break;
	case Green:
		retval = p.green; break;
	case Blue:
		retval = p.blue; break;
	default:
		assert(false);
	}

	return norm(retval);
}
void MPimp::setPixel(Channel ch, size_type x, size_type y, ch_pixel_type value)
{
	assert(false);
	/*
	m::PixelPacket &p = pixels[offset(ch, x, y)];
	
	value = denorm(value);
	
	switch (ch)
	{
	case Gray:
		p.red = value;
		p.green = value;
		p.blue = value;
		break;
	case Red:
		p.red = value; break;
	case Green:
		p.green = value; break;
	case Blue:
		p.blue = value; break;
	default:
		assert(false);
		}*/
}

ch_pixel_type DPimp::pixel(Channel ch, size_type x, size_type y)
{
	return dimage[offset(ch, x, y)];
}
void DPimp::setPixel(Channel ch, size_type x, size_type y, ch_pixel_type value)
{
	if (grayscale)
	{
		const size_t o = offset(Red, x, y);
		dimage[o] = dimage[o+1] = dimage[o+2] = value;
	}
	else
		dimage[offset(ch, x, y)] = value;
}

ch_pixel_type Image::pixel(Channel ch, size_type x, size_type y) const
{
	assert((ch == Gray) == _pimp->grayscale);
	return _pimp->pixel(ch, x, y);
}
void Image::setPixel(Channel ch, size_type x, size_type y,
		     ch_pixel_type value)
{
	assert((ch == Gray) == _pimp->grayscale);
	_pimp->setPixel(ch, x, y, value);
}

void Image::save(double squeeze) const
{
	if (!_pimp->outfilename.empty())
		_pimp->save(squeeze);
}

void MPimp::save(double) {}

inline double dabs(double d) { return d<0 ? -d : d; }
void DPimp::save(double squeeze)
{
	USELOG();
	LOG_DEBUG("Normalizing image...");
	double max=numeric_limits<double>::min();
	double min=numeric_limits<double>::max();
	double s=0;
	size_type i; double *d;
	for (i = 0, d=dimage; i<len; i++, d++)
	{
		if (*d > max) max=*d;
		if (*d < min) min=*d;
		s+=dabs(*d);
	}
	LOG_DEBUG("BEFORE: min=" << min << "; max=" << max << "; absavg=" << s/len);
	
	const double high = (dabs(max) > dabs(min) ? dabs(max) : dabs(min))*squeeze;
	const double c = 1/log(1+high);
	for (i = 0, d=dimage; i<len; i++, d++)
		*d = c*log(1 + dabs((*d)*squeeze));

	LOG_DEBUG("Creating image object");
	m::Image img(w, h, "RGB", m::DoublePixel, dimage);
	LOG_DEBUG("Saving '" << outfilename <<
		  "' (w=" << w << "; h=" << h << "; len=" << len << ')');
	img.write(outfilename);
}
